ItIron2023
react
我們昨天用了一個很簡單的例子展示useEffect可能造成的memory leak issue,今天我們來看一個老範例,這是所有的react教學都特別強調的一點,同時你也會在console中不斷看到相關的warning,但你是否真的理解為什麼這樣寫會造成問題、又會造成什麼問題呢?
讓我們很快的來看一下吧!
請觀察一下這個codesandbox。
這是一個相當簡單的清單,在App組件內我們渲染了Fruit組件組成的清單,每個Fruit組件都有著一個開關以及一個刪除按鈕,一切看起來都很正常,直到你試著做了以下的操作。
你會發現雖然Banana消失了,但Cherry組件卻展開了,請觀察以下的程式碼試著解釋並修復這個問題。
function App() {
const [fruits, setFruits] = useState([
{ id: "a", name: "Apple", color: "Red" },
{ id: "b", name: "Banana", color: "Yellow" },
{ id: "c", name: "Cherry", color: "Red" }
]);
const deleteFruit = (index) => {
const newFruits = [...fruits];
newFruits.splice(index, 1);
setFruits(newFruits);
};
return (
<div>
<h1>Using key as index might be a huge disaster</h1>
<ul>
{fruits.map((fruit, index) => (
<Fruit
key={index}
fruit={fruit}
deleteFruit={() => deleteFruit(index)}
/>
))}
</ul>
</div>
);
}
const Fruit = ({ fruit, deleteFruit }) => {
const [isOpen, toggle] = useState(false);
return (
<li>
<span onClick={() => toggle(!isOpen)}>{isOpen ? "▼" : "►"}</span>
{fruit.name}
<button onClick={deleteFruit}>Delete</button>
{isOpen && <div>Color: {fruit.color}</div>}
</li>
);
};
export default App;
雖然標題基本上已經劇透完畢了,相信你們也知道key是關鍵,但為什麼index作為key會造成這樣的問題呢? 這一樣又是個react的渲染機制,簡單來說在react渲染陣列時會依賴key作為辨識(identifier)元素的依據,同時react也會試圖去重複利用組件去提升渲染效率,因此當你今天刪除了key=1的組件,更新清單時react發現更新後key=1的組件其實還在(因為下方的Cherry遞補上來),為了達到最佳效率它便僅更新了其中一個props,也就是水果的名字,而其餘的state則因為重複使用而保存下來了。 這算是個常見但其實牽扯相當深的底層渲染邏輯,這邊僅僅是給一個相當粗略的說法,有興趣請自己去查完整的相關文獻!
既然了解原因了,那實際上你知道只要給他獨一無二的key值就可以幫助react辨別哪些組件真的遭到更新了,解法會相當相當的簡單
<ul>
{fruits.map((fruit, index) => (
<Fruit
key={fruit.id}
fruit={fruit}
deleteFruit={() => deleteFruit(index)}
/>
))}
</ul>
今天我們示範了為什麼用index作為key可能不是個好主意,在許多的教學中沒有給你明確的例子,只跟你說react會利用key作為判別組件狀態的幫手,我想今天這個例子應該能讓你稍微清楚一些,但我並不是說你永遠不能用index作為key,要是你今天的清單並不會有更新、刪除之類的操作,實際上用index作為key並不會有太大的問題,你要注意的僅是頻繁更新清單的情況!
本文章同步發布於個人部落格,有興趣的朋友也可以來逛逛~!